/*
 * Copyright (C) 2006 Johan Maasing johan at zoom.nu Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package nu.zoom.swing.desktop.frame;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JMenuBar;
import javax.swing.KeyStroke;
import nu.zoom.swing.desktop.WorkbenchFrameListener;

import nu.zoom.swing.desktop.common.BackendException;
import nu.zoom.swing.desktop.preferences.InvalidDataTypeException;
import nu.zoom.swing.desktop.preferences.Preferences;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Johan Maasing
 * 
 */
public class DetachableWorkbenchFrame extends AbstractWorkbenchFrame {

	private Log log = LogFactory.getLog(getClass());
	private final WorkbenchFrames workbenchFrames;
	private final String internalFramePreferencesKey;
	private final String externalFramePreferencesKey;
	private WorkbenchFrameDelegate delegate;
	private boolean detached = false;
	private final String preferencesKey;
	private final Preferences preferences;
	private final Desktop desktop;
	private static final String DETACHED = ".detached";

	/**
	 * @param content
	 * @param workbenchFrames
	 * @param preferences
	 * @param preferencesKey
	 */
	DetachableWorkbenchFrame(final JComponent content, final JMenuBar frameMenu, final boolean resizable,
			final boolean maximizable, final WorkbenchFrames workbenchFrames, final Preferences preferences,
			final String preferencesKey, final Desktop desktop, final Boolean detachedOverride) {
		super();
		this.preferences = preferences;
		this.preferencesKey = preferencesKey;
		internalFramePreferencesKey = preferencesKey + ".int";
		externalFramePreferencesKey = preferencesKey + ".ext";
		this.workbenchFrames = workbenchFrames;
		this.desktop = desktop;
		if (detachedOverride == null) {
			try {
				String detachedPrefsValue = preferences.getString(preferencesKey + DETACHED);
				detached = ((detachedPrefsValue != null) && (detachedPrefsValue.equals(Boolean.TRUE.toString())));
			} catch (InvalidDataTypeException e) {
				log.warn(e);
				// Use default value and ignore error
				detached = false;
			} catch (BackendException e) {
				log.warn(e);
				// Use default value and ignore error
				detached = false;
			}
		} else {
			detached = detachedOverride.booleanValue();
		}
		registerFrameKeybindings(content);
		if (detached) {
			delegate =
					new ExternalWorkbenchFrame(this, content, frameMenu, resizable, maximizable, preferences,
							externalFramePreferencesKey);
		} else {
			delegate =
					new InternalWorkbenchFrame(this, content, frameMenu, resizable, maximizable, preferences,
							internalFramePreferencesKey, workbenchFrames, desktop);
		}
		delegate.setTitle("");
	}

	private void registerFrameKeybindings(JComponent content) {
		final ActionListener disposeListener = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				dispose();
			}
		};
		content.registerKeyboardAction(disposeListener,
				KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
		content.registerKeyboardAction(disposeListener,
				KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);

		final ActionListener disposeAllListener = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				workbenchFrames.closeAll();
			}
		};
		content.registerKeyboardAction(disposeAllListener,
				KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK),
				JComponent.WHEN_IN_FOCUSED_WINDOW);
		content.registerKeyboardAction(disposeAllListener,
				KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK),
				JComponent.WHEN_IN_FOCUSED_WINDOW);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see nu.zoom.swing.desktop.frame.AbstractWorkbenchFrame#delegateDispose()
	 */
	@Override
	protected void delegateDispose() {
		try {
			preferences.setString(preferencesKey + DETACHED, Boolean.toString(detached));
		} catch (BackendException e) {
			log.warn(e);
		}
		delegate.dispose();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see nu.zoom.swing.desktop.frame.AbstractWorkbenchFrame#getTitle()
	 */
	@Override
	public String getTitle() {
		return delegate.getTitle();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * nu.zoom.swing.desktop.frame.AbstractWorkbenchFrame#setTitle(java.lang
	 * .String)
	 */
	@Override
	public void setTitle(String newTitle) {
		if (log.isTraceEnabled()) {
			log.trace("Trying to set frame title to: " + newTitle);
		}
		String title = workbenchFrames.getUniqueTitle(newTitle);
		delegate.setTitle(title);
		workbenchFrames.titleChanged(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see nu.zoom.swing.desktop.frame.AbstractWorkbenchFrame#isVisible()
	 */
	@Override
	public boolean isVisible() {
		return delegate.isVisible();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see nu.zoom.swing.desktop.frame.AbstractWorkbenchFrame#moveToFront()
	 */
	@Override
	public void moveToFront() {
		delegate.moveToFront();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * nu.zoom.swing.desktop.frame.AbstractWorkbenchFrame#setFrameIcon(javax
	 * .swing.Icon)
	 */
	@Override
	public void setFrameIcon(ImageIcon icon) {
		ArrayList<ImageIcon> icons = new ArrayList<ImageIcon>();
		icons.add(icon);
		setFrameIcon(icons);
	}

	@Override
	public void setFrameIcon(List<ImageIcon> icons) {
		delegate.setFrameIcon(icons);
	}

	@Override
	public void setVisible(boolean visible) {
		delegate.setVisible(visible);
	}

	@Override
	public boolean isDetached() {
		return detached;
	}

	@Override
	public void attach() {
		WorkbenchFrameDelegate internalDelegate =
				new InternalWorkbenchFrame(this, delegate.getContent(), delegate.getFrameMenu(),
						delegate.isResizable(), delegate.isMaximizable(), preferences, internalFramePreferencesKey,
						workbenchFrames, desktop);

		switchDelegates(internalDelegate);

		detached = false;
		fireAttached();
	}

	@Override
	public void detach() {
		WorkbenchFrameDelegate externalDelegate =
				new ExternalWorkbenchFrame(this, delegate.getContent(), delegate.getFrameMenu(),
						delegate.isResizable(), delegate.isMaximizable(), preferences, externalFramePreferencesKey);

		switchDelegates(externalDelegate);

		detached = true;
		fireDetached();
	}

	private void switchDelegates(WorkbenchFrameDelegate newDelegate) {
		delegate.savePreferencesAndDisposeOfNativeFrameWithoutCallbacks();
		String title = delegate.getTitle();
		newDelegate.setTitle(title);

		List<ImageIcon> icons = delegate.getFrameIcons();
		if ((icons != null) && (icons.size() > 0)) {
			newDelegate.setFrameIcon(icons);
		}

		delegate = newDelegate;
		delegate.setVisible(true);
	}

	protected synchronized void fireDetached() {
		if (log.isTraceEnabled()) {
			log.trace("Informing " + frameListeners.size() + " listeners that frame will dispose");
		}
		for (final WorkbenchFrameListener listener : frameListeners) {
			EventQueue.invokeLater(new Runnable() {

				@Override
				public void run() {
					listener.frameDetached(DetachableWorkbenchFrame.this);
				}
			});
		}
	}

	protected synchronized void fireAttached() {
		if (log.isTraceEnabled()) {
			log.trace("Informing " + frameListeners.size() + " listeners that frame will dispose");
		}
		for (final WorkbenchFrameListener listener : frameListeners) {
			EventQueue.invokeLater(new Runnable() {

				@Override
				public void run() {
					listener.frameAttached(DetachableWorkbenchFrame.this);
				}
			});
		}
	}
}
